﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Higgs.AutoSync
{
    public class SyncProcessor
    {
        private object _lock = new object();
        private FileSystemWatcher watcher;
        private string syncFolder = "D:\\";
        private string backupFolder = "E:\\AutoSync\\";
        private string[] ignorePath = 
        {
            "D:\\TEMP\\"
        };

        private List<string> directories;

        private int delay = 2000;

        private Dictionary<string, IDisposable> timeouts;

        public bool Start()
        {
            // Validate current setting
            if (false)
            {
                return false;    
            }

            timeouts = new Dictionary<string, IDisposable>();
            watcher = new FileSystemWatcher(syncFolder, "*.*")
            {
                IncludeSubdirectories = true,
                InternalBufferSize = 64000
            };

            // Copy current files & folder to backup directory.
            if (!Directory.Exists(backupFolder))
            {
                Directory.CreateDirectory(backupFolder);
            }
            else
            {
                var files = Directory.GetFiles(backupFolder, "*.*", SearchOption.TopDirectoryOnly);
                var folders = Directory.GetDirectories(backupFolder, "*", SearchOption.TopDirectoryOnly);

                foreach (var f in folders)
                {
                    try
                    {
                        Directory.Delete(f, true);    
                    }catch {}
                }
                foreach (var f in files)
                {
                    try
                    {
                        File.Delete(f);
                    }
                    catch { }
                }
            }

            DirectoryCopy(syncFolder, backupFolder, true);

            watcher.Created += watcher_Changed;
            watcher.Changed += watcher_Changed;
            watcher.Deleted += watcher_Changed;
            watcher.Renamed += watcher_Changed;
            watcher.EnableRaisingEvents = true;

            return true;
        }

        public void Stop()
        {
            if (timeouts != null)
            {
                foreach (var value in timeouts.Values)
                {
                    if (value != null) value.Dispose();
                }
            }
        }

        bool IsExcludePath(string filePath)
        {
            filePath = filePath + "\\";

            foreach (var path in ignorePath)
            {
                if (filePath.StartsWith(path)) return true;
            }

            return false;
        }

        string GetBackupFilePath(string path)
        {
            return Path.Combine(backupFolder, path.Substring(syncFolder.Length));
        }
        
        void watcher_Changed(object sender, FileSystemEventArgs e)
        {
            if (IsExcludePath(e.FullPath)) return;
            if (e.ChangeType == WatcherChangeTypes.Changed)
            {
                // Ignore change event in any directory.
                if (Directory.Exists(e.FullPath)) return;
            }

            lock (_lock)
            {
                if (e.ChangeType != WatcherChangeTypes.Deleted || e.ChangeType != WatcherChangeTypes.Renamed)
                {
                    // prevent multiple raise events.
                    if (timeouts.ContainsKey(e.FullPath)) timeouts[e.FullPath].Dispose();
                    timeouts[e.FullPath] = EasyTimer.SetTimeout(() => ProcessFile(e), delay);
                }
                else
                {
                    ProcessFile(e);
                }
            }
        }

        void ProcessFile(FileSystemEventArgs e)
        {
            Trace.TraceInformation("\"{0}\" is {1}.", e.FullPath, e.ChangeType);

            Task.Factory.StartNew(() =>
            {
                var backupPath = GetBackupFilePath(e.FullPath);
                var backupDir = backupPath.Substring(0, backupPath.LastIndexOf('\\') + 1);

                try
                {
                    switch (e.ChangeType)
                    {
                        case WatcherChangeTypes.Created:
                            if (e.ChangeType == WatcherChangeTypes.Created && !File.Exists(e.FullPath))
                            {
                                // New Directory
                                Directory.CreateDirectory(backupPath);
                                break;
                            }
                            if (!Directory.Exists(backupDir)) Directory.CreateDirectory(backupDir);

                            File.Copy(e.FullPath, backupPath, true);

                            break;
                        case WatcherChangeTypes.Changed:
                            File.Copy(e.FullPath, backupPath, true);

                            break;
                        case WatcherChangeTypes.Deleted:
                            if (Directory.Exists(backupPath)) Directory.Delete(backupPath);
                            else if (File.Exists(backupPath)) File.Delete(backupPath);

                            break;
                        case WatcherChangeTypes.Renamed:
                            var renameEventArgs = (RenamedEventArgs)e;
                            var oldBackupPath = GetBackupFilePath(renameEventArgs.OldFullPath);

                            if (Directory.Exists(renameEventArgs.FullPath))
                            {
                                Directory.Move(oldBackupPath, backupPath);
                            }
                            else
                            {
                                File.Move(oldBackupPath, backupPath);
                            }

                            break;
                    }
                }
                catch (Exception ex)
                {
                    Trace.TraceError("Unable to " + e.ChangeType + " file", ex.Message);
                }
            });
        }

        private void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs)
        {
            if (IsExcludePath(sourceDirName)) return;

            // Get the subdirectories for the specified directory.
            var dir = new DirectoryInfo(sourceDirName);
            var dirs = dir.GetDirectories();

            if (!dir.Exists)
            {
                throw new DirectoryNotFoundException("Source directory does not exist or could not be found: " + sourceDirName);
            }

            // If the destination directory doesn't exist, create it. 
            if (!Directory.Exists(destDirName))
            {
                Directory.CreateDirectory(destDirName);
            }

            // Get the files in the directory and copy them to the new location.
            var files = dir.GetFiles();
            foreach (var file in files)
            {
                if (IsExcludePath(file.FullName)) return;

                var temppath = Path.Combine(destDirName, file.Name);
                file.CopyTo(temppath, false);
            }

            // If copying subdirectories, copy them and their contents to new location. 
            if (!copySubDirs) return;

            foreach (var subdir in dirs)
            {
                var temppath = Path.Combine(destDirName, subdir.Name);
                DirectoryCopy(subdir.FullName, temppath, true);
            }
        }
    }
}
